﻿namespace Mockingbird.MVC.Helpers
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;
    using System.Web.Routing;
    using Core.Utilities.Cryptography;

    public static class CustomHelpers
    {
        private static readonly object syncLock = new object();

        private static string virtualDirectory = VirtualPathUtility.ToAbsolute("~/");
        private static string cssDir = virtualDirectory + "Content/Styles";
        private static string imageDir = virtualDirectory + "Content/images";
        private static string jsDir = virtualDirectory + "Scripts";
        private static string jsDirBundle = "~/Scripts/";
        private static string cssDirBundle = "~/Content/Styles";

        /// <summary>
        /// HTML helper that generates js script tag for a given file name. 
        /// It is not necessary for the file name to ends with ".js"
        /// This method searches in ~/Scripts/js
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="fileName"></param>
        /// <returns>HtmlString</returns>
        public static HtmlString Script(this HtmlHelper helper, string fileName)
        {
            InsertSlashTo(ref fileName);

            if (!fileName.EndsWith(".js"))
            {
                fileName += ".js";
            }

            var jsPath = string.Format("<script src='{0}{1}' type='text/javascript'></script>\n", jsDir, helper.AttributeEncode(fileName));
            return new HtmlString(jsPath);
        }

        /// <summary>
        /// HTML helper that generates css tag for a given file name. 
        /// It is not necessary for the file name to ends with ".css"
        /// This method searches in ~/Content/Styles
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="fileName"></param>
        /// <returns>HtmlString</returns>
        public static HtmlString CSS(this HtmlHelper helper, string fileName)
        {
            InsertSlashTo(ref fileName);

            if (!fileName.EndsWith(".css"))
            {
                fileName += ".css";
            }

            var jsPath = string.Format("<link rel='stylesheet' type='text/css' href='{0}{1}'/>\n", cssDir, helper.AttributeEncode(fileName));
            return new HtmlString(jsPath);
        }

        /// <summary>
        /// HTML helper that generates image tag for a given file name. 
        /// This method searches in ~/Content/images
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="fileName"></param>
        /// <returns>HtmlString</returns>
        public static HtmlString Image(this HtmlHelper helper, string fileName)
        {
            return Image(helper, fileName, string.Empty);
        }

        /// <summary>
        /// HTML helper that generates image tag for a given file name and attributes.  
        /// This method searches in ~/Content/images
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="fileName"></param>
        /// <param name="attributes"></param>
        /// <returns>HtmlString</returns>
        public static HtmlString Image(this HtmlHelper helper, string fileName, string attributes)
        {
            InsertSlashTo(ref fileName);
            fileName = string.Format("{0}{1}", imageDir, fileName);
            return new HtmlString(string.Format("<img src='{0}' {1} />", helper.AttributeEncode(fileName), attributes));
        }

        /// <summary>
        /// Html helper that creates an image tag and accepts params in a Dictionary like way.
        /// </summary>
        /// <param name="helper">Extension method to HtmlHelper</param>
        /// <param name="fileName">Image filename used for src</param>
        /// <param name="htmlAttributes">Dictionary with attributes</param>
        /// <returns>Html span element</returns>
        public static HtmlString Image(this HtmlHelper helper, string fileName, object htmlAttributes)
        {
            InsertSlashTo(ref fileName);
            fileName = string.Format("{0}{1}", imageDir, fileName);

            TagBuilder tagBuilder = new TagBuilder("img");
            tagBuilder.Attributes.Add("src", fileName);
            tagBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));

            return new HtmlString(tagBuilder.ToString(TagRenderMode.Normal));
        }

        /// <summary>
        /// Html helper for span tag element
        /// </summary>
        /// <param name="helper">Extension method to HtmlHelper</param>
        /// <param name="innerText">Text for the span</param>
        /// <param name="htmlAttributes">Dictionary with attributes</param>
        /// <returns>Html span element</returns>
        public static HtmlString Span(this HtmlHelper helper, string innerText, object htmlAttributes)
        {
            if (string.IsNullOrEmpty(innerText))
            {
                return new HtmlString(string.Empty);
            }

            TagBuilder tagBuilder = new TagBuilder("span");
            tagBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            tagBuilder.SetInnerText(innerText);

            return new HtmlString(tagBuilder.ToString(TagRenderMode.Normal));
        }

        /// <summary>
        /// Used for menu tab items. Determines which is current and applies the "active" style to it. 
        /// </summary>
        /// <param name="htmlHelper">Extention method</param>
        /// <param name="text">The menu tab text</param>
        /// <param name="action">The action method</param>
        /// <param name="controller">The controler</param>
        /// <returns>Link to controller/action</returns>
        public static HtmlString MenuItem(this HtmlHelper htmlHelper, string text, string action, string controller, object routeValues = null)
        {
            string ahref = string.Empty;

            routeValues = routeValues ?? new { area = string.Empty };

            var routeData = htmlHelper.ViewContext.RouteData;
            var currentAction = routeData.GetRequiredString("action");
            var currentController = routeData.GetRequiredString("controller");
            if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
            {
                ahref = htmlHelper.ActionLink(text, action, controller, routeValues, new { @class = "active" }).ToString();
            }
            else
            {
                ahref = htmlHelper.ActionLink(text, action, controller, routeValues, null).ToString();
            }

            return new HtmlString(ahref);
        }

        /// <summary>
        /// Creates a radio button from an enum. The first radio button is checked
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="htmlHelper"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(
            this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression)
        {
            return RadioButtonForEnum<TModel, TProperty>(htmlHelper, expression, null);
        }


        /// <summary>
        /// Creates a radio button from an enum. 
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="htmlHelper"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(
            this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression,
            string checkedName)
        {
            var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

            string[] names;

            if (metaData.ModelType.IsGenericType && metaData.ModelType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                names = Enum.GetNames(Nullable.GetUnderlyingType(metaData.ModelType));
            }
            else
            {
                names = Enum.GetNames(metaData.ModelType);
            }

            var sb = new StringBuilder();
            bool first = true;
            foreach (var name in names)
            {
                var description = name;

                MemberInfo[] memInfo;

                if (metaData.ModelType.IsGenericType && metaData.ModelType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    memInfo = Nullable.GetUnderlyingType(metaData.ModelType).GetMember(name);
                }
                else
                {
                    memInfo = metaData.ModelType.GetMember(name);
                }

                if (memInfo != null)
                {
                    var attributes = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
                    if (attributes != null && attributes.Length > 0)
                    {
                        description = ((DisplayAttribute)attributes[0]).Name;
                    }
                }

                var id = string.Format(
                    "{0}_{1}_{2}",
                    htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
                    metaData.PropertyName,
                    name);

                string radio;

                if (first || name.Equals(checkedName))
                {
                    radio = htmlHelper.RadioButtonFor(expression, name, new { id = id, @checked = true }).ToHtmlString();
                    first = false;
                }
                else
                {
                    radio = htmlHelper.RadioButtonFor(expression, name, new { id = id }).ToHtmlString();
                }

                sb.AppendFormat(
                    "{0}<label for=\"{1}\"> {2}</label><br />",
                    radio,
                    id,
                    HttpUtility.HtmlEncode(description));
            }

            return MvcHtmlString.Create(sb.ToString());
        }

        public static MvcHtmlString DropDownForEnum<TModel, TProperty>(
            this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression,
            object htmlAttributes = null)
        {
            return DropDownForEnum<TModel, TProperty>(htmlHelper, expression, null, htmlAttributes);
        }

        public static MvcHtmlString DropDownForEnum<TModel, TProperty>(
            this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression,
            string checkedName,
            object htmlAttributes = null)
        {
            var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            var names = Enum.GetNames(metaData.ModelType);

            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            foreach (var name in names)
            {
                var description = name;

                var memInfo = metaData.ModelType.GetMember(name);
                if (memInfo != null)
                {
                    var attributes = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
                    if (attributes != null && attributes.Length > 0)
                    {
                        description = ((DisplayAttribute)attributes[0]).Name;
                    }
                }

                dictionary.Add(name, description);
            }

            return htmlHelper.DropDownListFor(expression, new SelectList(dictionary, "key", "value", checkedName), htmlAttributes);
        }

        public static HtmlString Button(this HtmlHelper helper, string text, object htmlAttributes)
        {
            var tagBuilder = new TagBuilder("button");
            tagBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            tagBuilder.InnerHtml = text;

            return new HtmlString(tagBuilder.ToString(TagRenderMode.Normal));
        }

        #region private methods

        /// <summary>
        /// Function that checks if the filename starts with "/" and 
        /// if it doesn't - it adds it.
        /// Script, CSS and Image use it.
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns>string</returns>
        private static void InsertSlashTo(ref string fileName)
        {
            if (!fileName.StartsWith("/"))
            {
                fileName = fileName.Insert(0, "/");
            }

            ////return fileName;
        }

        #endregion
    }
}
